/***********************************************************************************************************************
 * Copyright (c) 2010 Ronald D. Kurr. Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 **********************************************************************************************************************/

package org.kurron.web.controller;

import java.io.IOException;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.kurron.web.service.AliasGenerator;
import org.kurron.web.service.MappingNotFoundDeviation;
import org.kurron.web.service.UrlBuilder;
import org.kurron.web.service.UrlMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

/**
 * This controller processes all requests for the WeeURL application.  There are three operations that the application
 * supports:
 * <ul>
 * <li>creating a new shortened URL</li>
 * <li>redirecting a shortened URL to the full URL</li>
 * <li>administration of shortened URLs</li>
 * </ul>
 *
 * We are using Spring MVC framework to simplify the work needed to be done.  We are using the annotation driven
 * approach to reduce the amount of configuration XML that needs to created.
 */
@Controller
public final class WeeUrlController
{
    /**
     * Contains the mappings between long and shortened URLs.
     */
    private final UrlMapper theMapper;

    /**
     * Generates shortened URLs from long ones.
     */
    private final AliasGenerator theAliasGenerator;

    /**
     * Knows how to properly build URLs.
     */
    private final UrlBuilder theBuilder;

    /**
     * Default constructor.
     * @param mapper URL mapper to use.
     * @param aliasGenerator alias generator to use.
     * @param builder responsible for properly constructing URL objects.
     */
    @Autowired
    public WeeUrlController( final UrlMapper mapper,
                             final AliasGenerator aliasGenerator,
                             final UrlBuilder builder )
    {
        theMapper = mapper;
        theAliasGenerator = aliasGenerator;
        theBuilder = builder;
    }

    /**
     * This method will present the user the home page of the application.
     * @return view name to render and any data it may need.
     */
    @RequestMapping( value = "/", method = RequestMethod.GET )
    public ModelAndView presentHomePage()
    {
        final ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName( "Home" );
        modelAndView.addObject( "model", new DataBean() );
        return modelAndView;
    }

    /**
     * This method will accept an alias to a URL and redirect the caller to the actual URL.
     * @param alias short alias to look up the long URL with.  Will be something like 1AB21C.
     * @param servletResponse response object to use.
     * @throws MappingNotFoundDeviation if the alias is not currently mapped to a proper URL.
     * @throws IOException if there is a problem redirecting the client to the long URL.
     */
    @RequestMapping( value = "/{alias}", method = RequestMethod.GET )
    public void redirectToLongUrl( @PathVariable final String alias,
                                                 final HttpServletResponse servletResponse ) throws MappingNotFoundDeviation, IOException
    {
        final URL original = theMapper.toOriginal( alias );
        servletResponse.sendRedirect( original.toExternalForm() );
    }

    /**
     * This method creates a new shorted URL from the provided long one.
     * @param longUrl URL to shorten.
     * @param servletRequest incoming request object.
     * @return view name to render and any data it may need.
     */
    @RequestMapping( value = "/newURL", method = RequestMethod.POST )
    public ModelAndView newWeeUrl( @RequestParam( "longUrl" ) final URL longUrl,
                                                              final HttpServletRequest servletRequest )
    {
        final ModelAndView modelAndView = new ModelAndView();
        try
        {
            final URL alias = constructAlias( longUrl, servletRequest );
            final DataBean model = new DataBean();
            model.setLongUrl( longUrl );
            model.setShortUrl( alias );
            modelAndView.setViewName( "NewUrl" );
            modelAndView.addObject( "model", model );
        }
        catch( MappingNotFoundDeviation deviation )
        {
            modelAndView.setViewName( "Error" );
            modelAndView.addObject( "error", deviation );
        }
        return modelAndView;
    }

    /**
     * This method construct a shortened URL from a longer one.
     * @param longUrl URL to shorten.
     * @param servletRequest the incoming request.
     * @return shortened URL.
     * @throws MappingNotFoundDeviation if there is a problem retrieving an existing alias.
     */
    private URL constructAlias( final URL longUrl, final HttpServletRequest servletRequest ) throws MappingNotFoundDeviation
    {
        final String host = servletRequest.getServerName();
        final int port = servletRequest.getServerPort();
        final String contextPath = servletRequest.getContextPath();
        final URL alias;
        final boolean alreadyShortened = theMapper.containsOriginal( longUrl );
        if ( alreadyShortened )
        {
            alias = theMapper.toAlias( longUrl, "http", host, port, contextPath );
        }
        else
        {
            final String aliasPath = theAliasGenerator.generateAlias( longUrl );
            alias = theBuilder.newUrl( "http", host, port, contextPath, aliasPath );
            theMapper.addMapping( aliasPath, longUrl );
        }
        return alias;
    }

    /**
     * Default exception handler.
     * @param e error that was trapped.
     * @return model name to render and any data it may need.
     */
    @ExceptionHandler( Exception.class )
    public ModelAndView errorPage( final Exception e )
    {
        final ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName( "Error" );
        modelAndView.addObject( "error", e );
        return modelAndView;
    }
}
